<?php defined('SYSPATH') or die('No direct script access.');
 
class Controller extends Controller_Core {

    protected $db;
    /** @var YoseminDao */
    public $dao;
    public $viewData = array();
    protected $contentLayout;
    protected $pageTemplate = 'pageTemplate';
    public $formName;

    /** @var Messages */
    public $msg;
    
    /** @var RegistrationService */
    private $seminarService;
    
    /** @var YoseminService */
    private $yoseminService;

    /** @var SessionData */
    private $sessionData;
    private $viewRendered = false;

    private $l10nLanguageCode;
    
    protected $viewPath;
    protected $controllerPath;
    public $pageTitle;
    
    protected $listActionName;
    public $entityName;
    
    public function __construct() {
        parent::__construct();
        
        $this->dao = YoseminDao::instance();
        
        $this->pagination = new Pagination(array());

        $this->sessionData = SessionData::instance();
        
        // (user|admin|org|reception)
        $applicationSpace = Router::$segments[0];
        
        $this->formName = 'basic/' . $applicationSpace . '/' . Router::$controller;

        // Messages
        $this->msg = Messages::instance();
        $this->viewData['msg'] = $this->msg;

        if (request::isPopup()) {
            $this->contentLayout = 'popupLayout';
            $this->pageTemplate = 'emptyTemplate';
        }
        else if ($applicationSpace == 'admin' || $applicationSpace == 'org' || $applicationSpace == 'reception') {
        	$this->pageTemplate = $applicationSpace . '/' . $applicationSpace . 'Template';
        	if (! $this->contentLayout) {
        		$this->contentLayout = $applicationSpace . '/' . $applicationSpace . 'Layout';
        	}
        }

        // Localization language
        $this->l10nLanguageCode = Router::$language;

        $this->viewData['l10nLanguage'] = $this->dao->getUnique('language', array('code' => $this->l10nLanguageCode));
        
        // List ordering
        $request = array_merge($_GET, $_POST);
        $orderBy = $request['orderBy'];
        $order = strToUpper($request['order']);
        $orderName = $request['orderName'];
        if ($order && $orderBy && $orderName) {
            $listOrder = new SortOrder();
            if ($orderBy && preg_match('/^[a-zA-Z_\\.]+$/', $orderBy)) {
                $listOrder->field = $orderBy;
            }
            if ($order && ($order == 'ASC' || $order == 'DESC')) {
                $listOrder->direction = $order;
            }
            $this->listOrder = $listOrder;
            $this->getSessionData()->setListOrder($orderName, $listOrder);
        }
        else {
            $this->listOrder = $this->getSessionData()->getListOrder($this->formName);
        }
        
        $this->viewPath = '/' . $applicationSpace . '/' . router::$controller;
        $this->controllerPath = '/' . $applicationSpace . '/' . router::$controller;
        
        if (method_exists($this, 'all')) {
        	$this->listActionName = 'all';
        }
        else if (method_exists($this, 'listing')) {
        	$this->listActionName = 'listing';
        }
    }
    
    protected function setDefaultListOrder($orderBy, $order='ASC') {
        $order = strToUpper($order);
        $this->defaultListOrder = new SortOrder($orderBy, $order);
        if ($this->listOrder == null) {
            $this->listOrder = $this->defaultListOrder;
        }
    }

    public function getLocalLanguageCode() {
        return Kohana::config('yosemin.local_language');
    }
    
    protected function redirect($where) {
        return $this->redirectWithLanguage($where, Router::$language);
    }
    
    protected function redirectWithLanguage($where, $language) {
    	// function url::redirect() caused loss of session.
    	/*
    	 $result = url::redirect($where);
    	if ($result === FALSE) {
    	echo 'Redirect failed';
    	}
    	*/
    	$uri = url::site_lang($language, $where, request::protocol());
    	if (request::is_ajax()) {
    		if (request::isPopup()) {
    			echo 'ajaxResponse.redirect = "' . $uri . '"';
    		}
    		else {
    			$ajaxResponseBuilder = new AjaxXmlResponseBuilder();
    			$ajaxResponseBuilder->documentOpen()->redirect($uri)->documentClose();
    		}
    	}
    	else {
    		header('Location: ' . $uri);
    	}
    	exit();
    }
    
    protected function renderView($viewName, $mainSubiewName=null) {
        $view = new View($this->pageTemplate);
        $view->set_global('msg', $this->msg);
        $view->set('contentLayout', $viewName);
        foreach ($this->viewData as $key => $value) {
            $view->set_global($key, $value);
        }
        if ($mainSubiewName != null) {
            $this->sessionData->addNavigationMark($mainSubiewName);
            $view->set_global('mainSubview', new View($mainSubiewName));
        }
        $view->render(TRUE);
        $this->viewRendered = true;
    }
    
    protected function renderSubview($mainSubiewName) {
        $this->renderView($this->contentLayout, $mainSubiewName);
    }
    
    public function renderForAjax($viewName, $updatedElementId) {
    	$view = new View($viewName);
    	$view->set_global('msg', $this->msg);
    	foreach ($this->viewData as $key => $value) {
    		$view->set_global($key, $value);
    	}
    	
    	// Write XML AJAX response
    	// $viewMarkup = $view->render(FALSE);
    	$ajaxBuilder = new AjaxXmlResponseBuilder();
    	$ajaxBuilder->sendHeaders()
	    		->documentOpen()
	    		->changesOpen()
		    	->updateView($updatedElementId, $view)
		    	->changesClose()
	    		->documentClose();
    	$this->viewRendered = true;
    }

    protected function isViewRendered() {
        return $this->viewRendered;
    }
    
    /**
     * Main point of this method is to convert empty strings to NULL values.
     * It is necessary for PostgreSQL integer fields.
     * It also converts dates from localized format to ISO format.
     */
    protected function postToDbValues() {
        $db = array();
        $this->convertPostValues($db, $_POST);
        return $db;
    }
    private function convertPostValues(&$db, $valueMap) {
    	foreach ($valueMap as $key => $value) {
    		if (is_array($value)) {
    			$db[$key] = array();
    			$this->convertPostValues($db[$key], $value);
    		}
    		else {
	    		if ($value == '') {
	    			$value = NULL;
	    		}
	    		else if (valid::isDate($value)) {
	    			$value = parse::date($value);
	    		}
	    		else if (is_string($value) && preg_match('/^[0-9]+(,[0-9]+)?$/', $value)) {
	    			$value = str_replace(',', '.', $value);
	    		}
	    		$db[$key] = $value;
    		}
    	}
    }
    
    /**
     * It is meant to be called when there are validation errors and the
     * processing will be terminated.
     */
    function publishErrors($subview) {
    	if (request::isPopup()) {
            echo $this->msg->messagesForAjax();
        }
        else {
            $this->renderSubview($subview);
        }
    }

    /**
     * It is meant to be called after successful POST request. It redirects
     * normal request from a browser or sends a success message in case of
     * an AJAX request.
     */
    function ajaxSuccessOrRedirect($redirectPath, $createdObjectArray, $sendMessages=TRUE) {
    	if (request::isPopup()) {
            if ($sendMessages) {
                echo $this->msg->messagesForAjax();
            }
            echo 'ajaxResponse.success=true;';
            echo 'ajaxResponse.createdObject = ' . json_encode($createdObjectArray) . ';';
        }
        else {
            $this->redirect($redirectPath);
        }
    }

    protected function getSessionData() {
        return $this->sessionData;
    }

    public function redirectBack() {
        if ($this->input->get('redirectedBack')) {
        	// The request was already redirected back, use a fallback URL that always works,
        	// otherwise it will be getting redirected in loop.
        	if (strpos($_SERVER['PATH_INFO'], '/user/') !== FALSE) {
        		$path = '/user/userSpace/welcome';
        	}
        	else if (strpos($_SERVER['PATH_INFO'], '/admin/') !== FALSE) {
        		$path = '/admin/adminSpace';
        	}
        	else if (strpos($_SERVER['PATH_INFO'], '/org/') !== FALSE) {
        		$path = '/org/orgSpace/welcome';
        	}
        	else {
        		$path = '/page';
        	}
        	$url = url::site($path, request::protocol());
        }
        else {
        	// Use the url this request came from.
        	$refererPath = substr($_SERVER['HTTP_REFERER'], strpos($_SERVER['HTTP_REFERER'], url::site('/')) + strlen(url::site('/')));
        	$url = $this->addURLParameter($refererPath, 'redirectedBack', '1');
        }
        $this->redirect($url);
    }
    
    /**
     * Adds an URL parameter, but doesn't replace the existing one with the same name.
     * TODO This method should be in the URL helper class, but there was a problem making custom MY_url.php - each request resulted in a redirection loop.
     */
    public function addURLParameter($url, $paramName, $paramValue) {
    	// first check whether the parameter is already
    	// defined in the URL so that we can just update
    	// the value if that's the case.
    
    	if (preg_match('/[?&]('.$paramName.')=[^&]*/', $url)) {
    
    		// parameter is already defined in the URL, so
    		// replace the parameter value, rather than
    		// append it to the end.
    		$url = preg_replace('/([?&]'.$paramName.')=[^&]*/', '$1='.$paramValue, $url) ;
    	} else {
    		// can simply append to the end of the URL, once
    		// we know whether this is the only parameter in
    		// there or not.
    		$url .= strpos($url, '?') ? '&' : '?';
    		$url .= $paramName . '=' . $paramValue;
    	}
    	return $url ;
    }

    public function getL10nLanguageCode() {
        return $this->l10nLanguageCode;
    }

    public function getAuthUser($field=NULL) {
        return $this->getSessionData()->getAuthUser($field);
    }
    
    public function getAuthOrg($field=NULL) {
    	return $this->getSessionData()->getAuthOrg($field);
    }

    /**
     * Returns authenticated user or substituted user.
     * The returned user is the one that should be used in user part.
     * This method should be used in user part only.
     */
    public function getAuthOrSubstituteUser($field=NULL) {
        $user = $this->getSessionData()->getUserSubstitute();
        if (! $user) {
            $user = $this->getSessionData()->getAuthUser();
        }
        if ($field) {
            return $user[$field];
        }
        return $user;
    }
    
    public function setUserField($fieldName, $fieldValue) {
        $user = $this->getSessionData()->getUserSubstitute();
        if ($user) {
            $user[$fieldName] = $fieldValue;
            $this->getSessionData()->setUserSubstitute($user);
        }
        if (! $user) {
            $user = $this->getSessionData()->getAuthUser();
            $user[$fieldName] = $fieldValue;
            $this->getSessionData()->setAuthUser($user);
        }
    }

    protected function mergeViewData($dataToMerge) {
        $this->viewData = array_merge($this->viewData, $dataToMerge);
    }
    
    protected function initPagination($query) {
    	if ($this->pagination->enabled) {
	        $totalItems = 0;
	        if (is_string($query)) {
	            if (preg_match('/^[a-zA-Z_]+$/', $query)) {
	                $queryString = 'SELECT count(*) FROM ' . $query;
	            }
	            else {
	                $queryString = $query;
	            }
	            $totalItems = $this->dao->queryCount($queryString);
	        }
	        else if ($query instanceof QueryAndParams) {
	            $queryString = sql::queryToCount($query->query);
	            $totalItems = $this->dao->queryCount($queryString, $query->params);
	        }
        
	        $this->pagination->initialize(array(
	                'total_items' => $totalItems
	        ));
    	}
    }

    protected function orderLimitAndOffset($queryString) {
        if ($this->listOrder != null) {
            $queryString .= ' ORDER BY ' . $this->listOrder->toSql();
            if ($this->defaultListOrderBy != null) {
                $queryString .= ', ' . $this->defaultListOrder->toSql();
            }
        }
        if ($this->pagination->enabled && $this->pagination->total_items > $this->pagination->items_per_page) {
            return $queryString . ' ' .
                    'LIMIT ' . $this->pagination->items_per_page . ' ' .
                    'OFFSET ' . $this->pagination->sql_offset;
        }
        return $queryString;
    }
    
    /** @return RegistrationService */
    protected function getSeminarService() {
    	if (! $this->seminarService) {
    		$this->seminarService = new SeminarService();
    	}
    	return $this->seminarService;
    }
    
    /** @return YoseminService */
    protected function getYoseminService() {
    	if (! $this->yoseminService) {
    		$this->yoseminService = new YoseminService();
    	}
    	return $this->yoseminService;
    }
    
    protected function renderMySubview($mainSubiewName) {
    	$this->renderView($this->contentLayout, $this->viewPath . '/' . $mainSubiewName);
    }
    
    protected function redirectToList($errorKey=NULL) {
    	if ($errorKey) {
    		$this->msg->error($errorKey);
    	}
    	$this->redirect($this->controllerPath . '/' . $this->listActionName);
    }
    
    protected function addViewData($dataMap) {
    	foreach ($dataMap as $key => $value) {
    		$this->viewData[$key] = $value;
    	}
    }
    
    protected function setSearchCriteria($criteria) {
    	$this->getSessionData()->setSearchCriteria($this->formName, $criteria);
    }
    protected function getSearchCriteria() {
    	$criteria = $this->getSessionData()->getSearchCriteria($this->formName);
    	if (! $criteria) {
    		return array();
    	}
    	return $criteria;
    }
    
    protected function searchData() {
    	if (method_exists($this, 'loadFormData')) {
    		call_user_func_array(array($this, 'loadFormData'), array());
    	}
    }
    
    public function searchForm() {
    	$this->contentLayout = 'admin/searchLayout';
    
    	$this->searchData();
    	$this->viewData['searchForm'] = $this->getSessionData()->getSearchCriteria($this->formName);
    	$this->renderSubview('admin/' . $this->entityName . '/' . $this->entityName . 'Search');
    }
    
    public function search() {
    	$searchForm = $this->postToDbValues();
    	unset($searchForm[auth::$FORM_ID]);
    	foreach ($searchForm as $key => $value) {
    		if ($value == null || $value === '') {
    			unset($searchForm[$key]);
    		}
    	}
    
    	$this->getSessionData()->setSearchCriteria($this->formName, $searchForm);
    	$this->redirectToList();
    }
    
    public function clearSearchCriteria() {
    	$this->getSessionData()->setSearchCriteria($this->formName, NULL);
    	$this->redirectToList();
    }
    
    public function hasSearchCriteria() {
    	$searchCriteria = $this->getSessionData()->getSearchCriteria($this->formName);
    	return ! empty($searchCriteria);
    }
}
